<?php

/**
 * This is project's console commands configuration for Robo task runner.
 *
 * @see http://robo.li/
 */
class RoboFile extends \Robo\Tasks
{
    private $repo_name;
    private $repo_url;
    private $checkout_sha;
    private $sha_path;
    private $repo_path;
    private $app_path;
    private $config_path;
    private $logs_path;
    private $env;
    private $branch;
    private $work_dir;
    private $setting_name;
    private $server;
    private $include_paths;
    private $exclude_files;

    public function deploy($repo_namespace, $server_env, $branch, $user_name, $ssh_host, $repo_url, $checkout_sha, $app_path, $repo_path, $work_dir, $setting_name, $app_logs_path = '', $app_configs_path = '', $include_paths = '', $exclude_files = '')
    {
        $this->say('部署开始！');
        $this->_init($repo_namespace, $server_env, $branch, $ssh_host, $repo_url, $checkout_sha, $app_path, $repo_path, $work_dir, $setting_name, $app_logs_path, $app_configs_path, $include_paths, $exclude_files);

        // 同一个 sha
        $queue_times = 0;
        while (true) {
            if (!$this->checkShaLock()) {
                $this->setShaLock();
                if ($queue_times) {
                    $this->_pubu('local 排队完成，耗时 ' . ($queue_times * 5) . ' 秒，现在开始部署 remote（' . $user_name . '）');
                } else {
                    $this->_pubu('开始部署（' . $user_name . '）');
                }
                break;
            }
            if (!$queue_times) {
                $this->_pubu('进入 local 排队（' . $user_name . '）');
            }
            $this->say('排队中...' . $queue_times++ . PHP_EOL);
            sleep(5);
        }

        if (is_dir($this->sha_path)) {
            $this->say('Sha path exist. Skip local.');
        } else {
            $this->deployLocal();
        }
        if ($this->repo_name == 'jinchun/deploy') {
            $this->_symlink($this->sha_path, $this->app_path);
        } else { // deploy 项目不需要 remote deploy

            $queue_times = 0;
            while (true) {
                if (!$this->checkRemoteLock()) {
                    $this->setRemoteLock();
                    if ($queue_times) {
                        $this->_pubu('remote 排队完成，耗时 ' . ($queue_times * 5) . ' 秒，现在开始部署到应用服务器（' . $user_name . '）');
                    }
                    break;
                }
                if (!$queue_times) {
                    $this->_pubu('进入 remote 排队（' . $user_name . '）');
                }
                $this->say('排队中...' . $queue_times++ . PHP_EOL);
                sleep(5);
            }

            $this->deployRemote();
        }

        $this->_pubu('部署完成');
        $this->say('部署结束！');
        $this->say('deploy_success.');
        $this->unlockShaLock();
        $this->unlockRemoteLock();
    }

    private function deployRemote()
    {
        $this->say('Remote deploy begin.');

        // 如果是 TP 或者其他非 Laravel 框架
        if (!$this->isLaravel() && $this->env == 'Product') {
            $index_file = $this->sha_path . '/web/index.php';
            if (file_exists($index_file)) {
                $this->say('set [' . $index_file . '] APP_DEBUG to false');
                $this->taskReplaceInFile($index_file)->regex("/\'APP_DEBUG\'.*(true)/")->to("'APP_DEBUG', false")->run();
                $this->taskReplaceInFile($index_file)->regex("/\'APP_DEBUG_OUTPUT\'.*(false)/")->to("'APP_DEBUG_OUTPUT', true")->run();
            }
        }

        if ($this->include_paths != '') {
            $include_paths = explode(PHP_EOL, $this->include_paths);
            foreach ($include_paths as $include_path) {
                $include_path = rtrim(trim($include_path), '/') . '/';
                $from_path    = $this->sha_path . $include_path;
                $command      = $this->taskRsync()
                    ->fromPath($from_path)
                    ->toPath($this->server . ':' . $this->app_path . '/' . $include_path)
                    ->owner()
                    ->group()
                    ->delete()
                    ->excludeVcs()
                    ->arg('-azviO')
                    ->option('no-perms');
                if ($this->exclude_files) {
                    $command = $command->exclude($this->exclude_files);
                }

                $command = $command->getCommand();
                $this->say('rsync command: ' . $command);
                $rsync = $this->taskExec('sudo /bin/su -s /bin/bash -c')->arg('"' . $command . '"')->arg('deploy')->run();
                if (!$rsync->wasSuccessful()) {
                    $this->abort('部署失败... : (');
                }
            }
        } else {
            $command = $this->taskRsync()
                ->dir($this->sha_path)
                ->fromPath($this->sha_path . '/')
                ->toPath($this->server . ':' . $this->app_path)
                ->owner()
                ->group()
                ->delete()
                ->excludeVcs()
                ->arg('-azviO')
                ->option('no-perms');

            if ($this->exclude_files) {
                $command = $command->exclude($this->exclude_files);
            }

            $command = $command->getCommand();
            $this->say('rsync command: ' . $command);
            $rsync = $this->taskExec('sudo /bin/su -s /bin/bash -c')->arg('"' . $command . '"')->arg('deploy')->run();

            // 如果是 TP 或者其他非 Laravel 框架
            if (!$this->isLaravel() && $this->env == 'Product') {
                $index_file = $this->sha_path . '/web/index.php';
                if (file_exists($index_file)) {
                    $this->say('rollback [' . $index_file . '] APP_DEBUG to true');
                    $this->taskReplaceInFile($index_file)->regex("/\'APP_DEBUG\'.*(false)/")->to("'APP_DEBUG', true")->run();
                    $this->taskReplaceInFile($index_file)->regex("/\'APP_DEBUG_OUTPUT\'.*(false)/")->to("'APP_DEBUG_OUTPUT', true")->run();
                }
            }

            if (!$rsync->wasSuccessful()) {
                $this->abort('部署失败... : (');
            }
        }
    }

    private function deployLocal()
    {
        $this->_clone();
        $this->_composerInstall();
        $this->_removeGitDir();
        $this->_afterScripts();
        $this->_symlinks();

        $this->unlockShaLock();
    }

    public function _init($repo_name, $env, $branch, $ssh_host, $repo_url, $checkout_sha, $app_path, $repo_path, $work_dir, $setting_name, $app_logs_path = '', $app_configs_path = '', $include_paths = '', $exclude_files = '')
    {
        $this->env           = $env;
        $this->repo_name     = $repo_name;
        $this->branch        = $branch;
        $this->repo_path     = $repo_path;
        $this->repo_url      = $repo_url;
        $this->checkout_sha  = $checkout_sha;
        $this->sha_path      = $this->repo_path . '/' . $env . '_' . $checkout_sha;
        $this->app_path      = $app_path;
        $this->config_path   = $app_configs_path;
        $this->logs_path     = $app_logs_path;
        $this->work_dir      = $work_dir;
        $this->setting_name  = $setting_name;
        $this->server        = $ssh_host;
        $this->include_paths = $include_paths ? $include_paths : '';
        $exclude_files       = $exclude_files ? $exclude_files : '';
        if (!empty($exclude_files)) {
            $this->exclude_files = explode("\r\n", $exclude_files);
        }
    }

    private function checkShaLock()
    {
        return file_exists('storage/app/' . $this->checkout_sha . '.lock');
    }

    private function setShaLock()
    {
        $this->_touch('storage/app/' . $this->checkout_sha . '.lock');
    }

    private function unlockShaLock()
    {
        $this->_remove('storage/app/' . $this->checkout_sha . '.lock');
    }

    private function checkRemoteLock()
    {
        return file_exists('storage/app/' . $this->server . '_' . str_replace('/', '_', $this->app_path) . '.lock');
    }

    private function setRemoteLock()
    {
        $this->_touch('storage/app/' . $this->server . '_' . str_replace('/', '_', $this->app_path) . '.lock');
    }

    private function unlockRemoteLock()
    {
        $this->_remove('storage/app/' . $this->server . '_' . str_replace('/', '_', $this->app_path) . '.lock');
    }

    private function _afterScripts()
    {
        // 一些后置执行的脚本
    }

    private function _composerInstall()
    {
        if (file_exists($this->sha_path . '/composer.json')) {
            $this->say('Composer Install');
            $composer_install = $this->taskComposerInstall('composer')->dir($this->sha_path)->run();
            if (!$composer_install->wasSuccessful()) {
                $this->abort('`composer install` failed.');
            }
            $dump_autoload = $this->taskComposerDumpAutoload('composer')->dir($this->sha_path)->run();
            if (!$dump_autoload->wasSuccessful()) {
                $this->abort('`composer dump-autoload` failed.');
            }
        }
    }

    private function _removeGitDir()
    {
        $this->say('Remove Git Dir');
        $remove_git = $this->_remove($this->sha_path . '/.git');
        if (!$remove_git->wasSuccessful()) {
            $this->abort('`remove .git` failed');
        }
    }

    private function _clone()
    {
        $this->say('Clone Repo');
        $clone_run = $this->taskGitStack()->cloneRepo($this->repo_url, $this->sha_path)->run();
        if (!$clone_run->wasSuccessful()) {
            $this->abort('`git clone` failed.');
        }
        $checkout_run = $this->taskGitStack()->dir($this->sha_path)->checkout($this->branch)->run();
        if (!$checkout_run->wasSuccessful()) {
            $this->abort('`git checkout branch` failed.');
        }
    }

    private function isLaravel()
    {
        return strpos($this->config_path, '.env') !== false;
    }

    private function _symlinks()
    {
        $this->say('Symlinks');

        if (!empty($this->config_path)) {
            if ($this->isLaravel()) { // laravel/lumen
                $this->_remove($this->sha_path . '/.env');
                $this->_symlink($this->config_path, $this->sha_path . '/.env');
                if (!empty($this->logs_path)) {
                    $this->_remove($this->sha_path . '/storage/logs');
                    $this->_symlink($this->logs_path, $this->sha_path . '/storage/logs');
                }
            } else {
                // tp or others
                $this->_remove($this->sha_path . '/app/Common/Conf');
                $this->_symlink($this->config_path, $this->sha_path . '/app/Common/Conf');

                if (!empty($this->logs_path)) {
                    $this->_remove($this->sha_path . '/logs');
                    $this->_symlink($this->logs_path, $this->sha_path . '/logs');

                    $paths = ['cache', 'consolecache'];
                    foreach ($paths as $path) {
                        $this->_remove($this->sha_path . '/' . $path);
                        $this->_mkdir($this->sha_path . '/' . $path);
                        $this->_chmod($this->sha_path . '/' . $path, 0777);
                    }
                    $this->_remove($this->sha_path . '/files');
                    $this->_symlink(rtrim($this->config_path, '/') . '/../files', $this->sha_path . '/files');
                }
            }
        }

        $chown = $this->taskExec('sudo /bin/chown -R')->arg('nginx.nginx')->arg($this->sha_path)->run();
        if (!$chown->wasSuccessful()) {
            $this->abort('`chown nginx.nginx` failed.');
        }
    }

    private function _pubu($msg)
    {
        $this->say($msg);
        $msg  = '【 ' . $this->repo_name . ' - ' . $this->branch . ' 】【' . $this->setting_name . '】【 ' . $this->env . ' 】' . $msg;
        $pubu = <<<PUBU
curl https://hooks.pubu.im/services/prpnk9xel3rxf8m -X POST --data-urlencode  'payload={"text": "$msg"}'
PUBU;
        $this->_exec($pubu);
    }

    private function abort($msg)
    {
        $this->say('Whoops! ' . $msg);
        $this->_pubu($msg);
        $this->unlockShaLock();
        $this->unlockRemoteLock();
        $this->_deleteDir($this->sha_path);
        exit;
    }
}
